/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * Copyright (c) 2008-2009, The KiWi Project (http://www.kiwi-project.eu)
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 * - Redistributions of source code must retain the above copyright notice,
 *   this list of conditions and the following disclaimer.
 * - Redistributions in binary form must reproduce the above copyright notice,
 *   this list of conditions and the following disclaimer in the documentation
 *   and/or other materials provided with the distribution.
 * - Neither the name of the KiWi Project nor the names of its contributors
 *   may be used to endorse or promote products derived from this software
 *   without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 *
 * Contributor(s):
 *
 *
 */
package kiwi.core.services.facading;

import kiwi.core.api.facading.FacadingService;
import kiwi.core.api.triplestore.LiteralService;
import kiwi.core.api.triplestore.ResourceService;
import kiwi.core.api.triplestore.TripleStore;
import kiwi.core.exception.NamespaceResolvingException;
import kiwi.core.exception.NonUniqueRelationException;
import kiwi.core.exception.ResourceResolvingException;
import kiwi.core.model.annotations.RDF;
import kiwi.core.model.annotations.RDFInverse;
import kiwi.core.model.content.MediaContent;
import kiwi.core.model.content.TextContent;
import kiwi.core.model.entity.KiWiFacade;
import kiwi.core.model.rdf.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.*;


/**
 * This class implements an invocation handler to be used for
 * proxy classes that delegate to a content item and to data in
 * the triple store. It has to be constructed using the triple
 * store implementation as parameter. Interfaces that make use of
 * this invocation handler need to be annotated with the
 * <code>KiWiFacade</code> annotation.
 *
 * @author Sebastian Schaffert
 */
public class KiWiInvocationHandler implements InvocationHandler {


    private TripleStore tripleStore;

    private ResourceService resourceService;

    private FacadingService facadingService;

    private LiteralService literalService;

    private KiWiResource item;

    private final HashMap<String,Object> fieldCache;

    private Logger log = LoggerFactory.getLogger(KiWiInvocationHandler.class);

    /**
     * Indicates if the cache is done not, by defaul is true.
     */
    private boolean useCache;

    public KiWiInvocationHandler(KiWiResource item, TripleStore tripleStore, ResourceService resourceService, LiteralService literalService, FacadingService facadingService) {
        this.item            = item;
        this.tripleStore     = tripleStore;
        this.resourceService = resourceService;
        this.literalService  = literalService;
        this.facadingService = facadingService;

        fieldCache  = new HashMap<String,Object>();

        // disable cache, it does not work well with deleted triples ...
        useCache = false;
    }

    /**
     * Indicates if the cache is allow or not.
     *
     * @return the useCache true if the cache is done.
     */
    public boolean isUseCache() {
        return useCache;
    }

    /**
     * Used to enable/disable the cache mechanism.
     *
     * @param useCache true foe enable cache, false - no cache.
     */
    public void setUseCache(boolean useCache) {
        this.useCache = useCache;
    }

    /**
     * @return the item
     */
    public KiWiResource getItem() {
        return item;
    }



    /**
     * @param item the item to set
     */
    public void setItem(KiWiResource item) {
        this.item = item;
    }

    /**
     * Invoke the invocation handler for the given proxy object,
     * method, and arguments. In order to execute the passed
     * method, this method does the following: - if the method
     * has a <code>RDF</code> annotation or if it is a setter and
     * the corresponding getter has a <code>RDF</code>
     * annotation, we try to retrieve the appropriate value by
     * querying the triple store and converting the triple store
     * data to the return type of the method; if the return type
     * is again an interface
     *
     * @see java.lang.reflect.InvocationHandler#invoke(java.lang.Object,
     *      java.lang.reflect.Method, java.lang.Object[])
     * @see RDF
     */
    public Object invoke(Object proxy, Method method, Object[] args) throws InstantiationException, IllegalAccessException  {
        // caching
        final String fieldName = method.getName().startsWith("get") || method.getName().startsWith("set") ?
                method.getName().substring(3) : method.getName();

        if(useCache && method.getName().startsWith("get")) {
            if(fieldCache.get(fieldName) != null) {
                return fieldCache.get(fieldName);
            }
        }

        if(fieldName.equals("hashCode")) {
            return item.hashCode();
        } else if(fieldName.equals("equals")) {
            final Object other = args[0];
            if(other !=null && other.getClass().equals(proxy.getClass()) && other.hashCode() == proxy.hashCode()) {
                return true;
            } else {
                return false;
            }
        } else if(fieldName.equals("toString")) {
            return "facade with delegate to "+item.toString();
        } else if(fieldName.equals("Delegate")) {
            return item;
        }

        String[] rdf_property = null;
        boolean inverse = false;

        // look for RDF annotation and extract the property from it; if not on the getter, look
        // for the corresponding setter and check whether it has a @RDF annotation; if neither has,
        // throw an IllegalArgumentException
        if(method.getAnnotation(RDF.class) != null) {
            rdf_property = method.getAnnotation(RDF.class).value();
        } else if(method.getAnnotation(RDFInverse.class) != null) {
            rdf_property = method.getAnnotation(RDFInverse.class).value();
            inverse = true;
        } else {
            try {
                final Method dualMethod = getCorrespondingGetterOrSetter(method);

                if(dualMethod.getAnnotation(RDF.class) != null) {
                    rdf_property = dualMethod.getAnnotation(RDF.class).value();
                } else if(dualMethod.getAnnotation(RDFInverse.class) != null) {
                    rdf_property = dualMethod.getAnnotation(RDFInverse.class).value();
                    inverse = true;
                }
            } catch(final NoSuchMethodException ex) {

            }


        }



        // distinguish getters and setters
        if(method.getName().startsWith("get") && method.getParameterTypes().length == 0) {

            // *** get the value of a certain RDF property ***

            final Class<?> returnType   = method.getReturnType();
            final Type typeOfGeneric = method.getGenericReturnType();

            // we believe that the result is universal for each property 
            // and therefore just return the result for the firstly defined property
            try {
                final Object result = transform(returnType, typeOfGeneric, item, rdf_property[0], inverse);

                if(useCache) {
                    fieldCache.put(fieldName,result);
                }
                return result;
            } catch(ResourceResolvingException ex) {
                log.debug("property {} does not exist, returning value null");
                return null;
            } catch (NamespaceResolvingException e) {
                // this error cannot be thrown since we are passing the URI, not the namespace:name argument
                log.error("namespace could not be resolved even though no namespace information was given");
                return null;
            }


        } else if(method.getName().startsWith("set") && method.getParameterTypes().length == 1) {

            // add to cache
            if(useCache) {
                fieldCache.put(fieldName,args[0]);
            }

            // *** set the value of a certain RDF property

            final Class paramType = method.getParameterTypes()[0];


            if (args[0] == null ) {
                // if argument is not a base type, and value is null, simply remove the relation
                for(String v : rdf_property) {
                    // first remove all properties prop that have this subject;
                    try {
                        for(final KiWiTriple t : resourceService.listOutgoing(item, v)) {
                            tripleStore.removeTriple(t);
                        }
                    } catch(NamespaceResolvingException ex) {
                        // this error cannot be thrown since we are passing the URI, not the namespace:name argument
                        log.error("namespace could not be resolved even though no namespace information was given");
                    } catch (ResourceResolvingException e) {
                        // in case the resource v does not exist, we don't need to do anything
                        log.debug("the property resource {} does not exist, so there are no triples to remove",v);
                    }
                }
            } else if( FacadeUtils.isBaseType(paramType) && !inverse) {
                for(String v : rdf_property) {
                    try {
                        if(args[0] == null || "".equals(args[0])) {
                            resourceService.setProperty(item,  v, null);
                        } else {
                            resourceService.setProperty(item, v, FacadeUtils.transformFromBaseType(args[0]));
                        }
                    } catch(NamespaceResolvingException ex) {
                        log.error("namespace could not be resolved even though no namespace information was given");
                    }
                }
            } else if (FacadeUtils.isTextContent(paramType) && !inverse) {
                for(String v : rdf_property) {

                    // create a new TextContentLiteral
                    KiWiTextContentLiteral l = literalService.createTextContentLiteral((TextContent)args[0]);

                    // then create a new triple for this property, subject, and object
                    updateOutgoingSingle(item,v,l);
                 }
            } else if (FacadeUtils.isMediaContent(paramType) && !inverse) {
                for(String v : rdf_property) {

                    // create a new TextContentLiteral
                    KiWiMediaContentLiteral l = literalService.createMediaContentLiteral((MediaContent) args[0]);

                    // then create a new triple for this property, subject, and object
                    updateOutgoingSingle(item,v,l);
                }
            } else if (FacadeUtils.isKiWiNode(paramType) && !inverse) {
                for(String v : rdf_property) {
                    updateOutgoingSingle(item,v,(KiWiNode)args[0]);
                }
            } else if (FacadeUtils.isKiWiResource(paramType) && inverse) {
                for(String v : rdf_property) {
                    updateIncomingSingle(item,v,(KiWiResource)args[0]);
                }
            } else if (FacadeUtils.isKiWiFacade(paramType) && !inverse) {
                for(String v : rdf_property) {
                    updateOutgoingSingle(item,v,((KiWiFacade)args[0]).getDelegate());
                }
            } else if (FacadeUtils.isKiWiFacade(paramType) && inverse) {
                for(String v : rdf_property) {
                    updateIncomingSingle(item,v,((KiWiFacade)args[0]).getDelegate());
                }
            } else if (FacadeUtils.isCollection(paramType)) {
                for(String v : rdf_property) {
                    final Collection c = (Collection)args[0];

                    // first remove all properties prop that have this subject;
                    try {
                        for(final KiWiTriple t : resourceService.listOutgoing(item,v)) {
                            tripleStore.removeTriple(t);
                        }
                    } catch (NamespaceResolvingException e) {
                        log.error("namespace resolution error for property {}, but no namespace given; programming error",v);
                    } catch (ResourceResolvingException e) {
                        log.debug("not removing triples, because property does not exist");
                    }

                    final KiWiUriResource prop = resourceService.createUriResource(v);

                    // then add each of the elements in the collection as new triple with prop
                    for (final Object o : c) {
                        if ( FacadeUtils.isBaseType(o.getClass()) && !inverse) {

                            final KiWiLiteral l = literalService.createLiteral(o);
                            tripleStore.createTriple(item, prop, l, item.getContext());

                        } else if (FacadeUtils.isKiWiFacade(o.getClass()) && !inverse) {

                            tripleStore.createTriple(item, prop, ((KiWiFacade) o).getDelegate(),item.getContext());

                        } else if (FacadeUtils.isKiWiFacade(o.getClass()) && inverse) {

                            tripleStore.createTriple(((KiWiFacade) o).getDelegate(), prop,item,item.getContext());

                        } else if (FacadeUtils.isKiWiNode(o.getClass()) && !inverse) {

                            tripleStore.createTriple(item, prop, (KiWiNode)o, item.getContext());

                        } else if (FacadeUtils.isKiWiResource(o.getClass()) && inverse) {

                            tripleStore.createTriple((KiWiResource)o, prop, item, item.getContext());

                        } else if (FacadeUtils.isTextContent(o.getClass()) && !inverse) {

                            KiWiTextContentLiteral l = literalService.createTextContentLiteral((TextContent)o);
                            tripleStore.createTriple(item, prop, l, item.getContext());

                        } else if (FacadeUtils.isMediaContent(o.getClass()) && !inverse) {

                            KiWiMediaContentLiteral l = literalService.createMediaContentLiteral((MediaContent)o);
                            tripleStore.createTriple(item, prop, l, item.getContext());

                        } else if(inverse) {
                            throw new IllegalArgumentException("method "+method.getName()+": @RDFInverse not supported for parameter type "+paramType.getName());
                        } else {

                            throw new IllegalArgumentException("the type "
                                    + o.getClass().getName()
                                    + " is not supported in collections");
                        }
                    }
                }
            } else if(inverse){
                throw new IllegalArgumentException("method "+method.getName()+": @RDFInverse not supported for parameter type "+paramType.getName());
            } else {
                throw new IllegalArgumentException("method "+method.getName()+": unsupported parameter type "+paramType.getName());
            }

            return null;

        } else {
            throw new IllegalArgumentException("only getter and setter methods are supported by the TripleStoreInvocationHandler");
        }

    }

    /**
     * Return the dually corresponding getter/setter to the passed setter/getter
     *
     * @param method
     * @return
     * @throws IllegalArgumentException
     * @throws NoSuchMethodException
     */
    private Method getCorrespondingGetterOrSetter(Method method) throws NoSuchMethodException {
        final Class cls = method.getDeclaringClass();

        String  methodName = null;
        Class[] paramTypes = null;
        if(method.getName().startsWith("get")) {
            methodName = "set" + method.getName().substring(3);
            paramTypes = new Class[1];
            paramTypes[0] = method.getReturnType();
        } else if(method.getName().startsWith("set")) {
            methodName = "get" + method.getName().substring(3);
            paramTypes = new Class[0];
        } else {
            throw new NoSuchMethodException("the passed method was not a getter or setter");
        }
        return cls.getMethod(methodName, paramTypes);
    }


    /**
     * Update the relation entity -> prop_uri -> ... with the new object; removes all existing relations of this
     * property_uri and creates a new triple.
     *
     */
    private void updateOutgoingSingle(KiWiResource entity, String prop_uri, KiWiNode object) {
        final KiWiUriResource prop = resourceService.createUriResource(prop_uri);
        // first remove all properties prop that have this subject;
        try {
            for(final KiWiTriple t : resourceService.listOutgoing(item, "<" + prop_uri + ">")) {
                tripleStore.removeTriple(t);
            }
        } catch(NamespaceResolvingException ex) {
            // does not happen, no namespace
        } catch(ResourceResolvingException ex) {
            // does not happen, created before
        }
        // then create a new triple for this property, subject, and object
        tripleStore.createTriple(entity, prop, object, entity.getContext());
    }


    /**
     * Update the relation ... -> prop_uri -> entity with the new subject; removes all existing relations of this
     * property_uri and creates a new triple.
     *
     */
    private void updateIncomingSingle(KiWiResource entity, String prop_uri, KiWiResource object)  {
        final KiWiUriResource prop = resourceService.createUriResource(prop_uri);
        // first remove all properties prop that have this subject;
        try {
            for(final KiWiTriple t : resourceService.listIncoming(item, "<" + prop_uri + ">")) {
                tripleStore.removeTriple(t);
            }
        } catch(NamespaceResolvingException ex) {
            // does not happen, no namespace
        } catch(ResourceResolvingException ex) {
            // does not happen, created before
        }
        // then create a new triple for this property, subject, and object
        tripleStore.createTriple(object, prop, entity, entity.getContext());

    }


    /**
     * Helper method to transform the object reachable via
     * rdf_property from r to the given returnType; if the
     * returnType is a collection, it is also necessary to
     * provide the generic type. The KiWiEntityManager is used
     * for further querying.<br>
     * Please note that if the <code>returnType</code>is a
     * collection you <b>must</b> use a concrete class (e.g.
     * <code>java.util.ArrayList</code>) not an abstract class or
     * interface.
     *
     * @param <C>
     * @param returnType
     * @param typeOfGeneric
     * @param rdf_property
     * @return
     * @throws InstantiationException
     * @throws IllegalAccessException
     * @throws NonUniqueRelationException
     */
    @SuppressWarnings("unchecked")
    private <C,D extends KiWiFacade> C transform(Class<C> returnType, Type typeOfGeneric, KiWiResource entity, String rdf_property, boolean inverse)
                                                                                            throws ResourceResolvingException, NamespaceResolvingException, IllegalAccessException, InstantiationException {
        // should not happen actually
        if(entity == null) {
            return null;
        }

        if( FacadeUtils.isBaseType(returnType) && !inverse) {
            // if the return type is string or primitive, get the literal value of the property and
            // transform it appropriately
            final String value = resourceService.getProperty(entity, rdf_property );

            try {
                // transformation to appropriate primitive type
                final C result = FacadeUtils.transformToBaseType(value, returnType);

                return result;
            } catch(final IllegalArgumentException ex) {
                return null;
            }

        } else if(FacadeUtils.isTextContent(returnType) && !inverse) {

            KiWiTextContentLiteral object = queryOutgoingSingle(entity,rdf_property,KiWiTextContentLiteral.class);

            if(object != null) {
                return (C) object.getTextContent();
            } else {
                return null;
            }

        } else if(FacadeUtils.isMediaContent(returnType) && !inverse) {

            KiWiMediaContentLiteral object = queryOutgoingSingle(entity,rdf_property,KiWiMediaContentLiteral.class);

            if(object != null) {
                return (C) object.getMediaContent();
            } else {
                return null;
            }


        } else if(FacadeUtils.isKiWiNode(returnType) && !inverse) {

            return (C) queryOutgoingSingle(entity,rdf_property,returnType);

        } else if(FacadeUtils.isKiWiNode(returnType) && inverse) {

            return (C) queryIncomingSingle(entity,rdf_property,returnType);

        } else if(FacadeUtils.isKiWiFacade(returnType) && !inverse) {
            // for KiWi entities, we retrieve the resource that is targeted by this property (by using getObject)
            // and create a query on the triple store using createQuery() and the resource's uri that returns the
            // result in the appropriate type (can e.g. be again a proxy using this invocation handler!)

            KiWiResource object = queryOutgoingSingle(entity,rdf_property,KiWiResource.class);

            if(object != null) {
                return (C)facadingService.createFacade(object, (Class<D>)returnType);
            } else {
                return null;
            }

        } else if(FacadeUtils.isKiWiFacade(returnType) && inverse) {
            // for KiWi entities, we retrieve the resource that is targeted by this property (by using getObject)
            // and create a query on the triple store using createQuery() and the resource's uri that returns the
            // result in the appropriate type (can e.g. be again a proxy using this invocation handler!)

            KiWiResource subject = queryIncomingSingle(entity,rdf_property,KiWiResource.class);

            if(subject != null) {
                return (C)facadingService.createFacade(subject, (Class<D>)returnType);
            } else {
                return null;
            }

        } else if(FacadeUtils.isCollection(returnType)) {
            // if we have a collection, we try to infer the generic type of its contents and
            // use this to generate values; if the generic type is a kiwi entity, we issue
            // a createQuery to the tripleStore to retrieve the corresponding values; if the
            // generic type is a base type, we transform the results to the base type and query for
            // literals
            if (typeOfGeneric instanceof ParameterizedType) {
                final ParameterizedType t = (ParameterizedType) typeOfGeneric;
                final Class tCls = (Class) t.getActualTypeArguments()[0];
                if (FacadeUtils.isKiWiFacade(tCls) && !inverse) {

                    return (C) createCollection(
                            returnType,
                            facadingService.createFacade(queryOutgoingAll(entity,rdf_property,KiWiResource.class),tCls)
                    );

                } else if (FacadeUtils.isKiWiFacade(tCls) && inverse) {

                    return (C) createCollection(
                            returnType,
                            facadingService.createFacade(queryIncomingAll(entity,rdf_property,KiWiResource.class),tCls)
                    );


                } else if (FacadeUtils.isKiWiNode(tCls) && !inverse) {

                    return (C) createCollection(
                            returnType,
                            queryOutgoingAll(entity,rdf_property,tCls)
                    );


                } else if (FacadeUtils.isKiWiNode(tCls) && inverse) {

                    return (C) createCollection(
                            returnType,
                            queryIncomingAll(entity, rdf_property, tCls)
                    );


                } else if (FacadeUtils.isTextContent(tCls) && !inverse) {

                    final Collection result = createCollection(returnType,Collections.EMPTY_SET);
                    for(KiWiTextContentLiteral l : queryOutgoingAll(entity,rdf_property,KiWiTextContentLiteral.class)) {
                        result.add(l.getTextContent());
                    }
                    return (C) result;

                } else if (FacadeUtils.isMediaContent(tCls) && !inverse) {

                    final Collection result = createCollection(returnType,null);
                    for(KiWiMediaContentLiteral l : queryOutgoingAll(entity,rdf_property,KiWiMediaContentLiteral.class)) {
                        result.add(l.getMediaContent());
                    }
                    return (C) result;

                } else if(inverse) {
                    throw new IllegalArgumentException("@RDFInverse not supported for mappings of type "+rdf_property);
                } else if( FacadeUtils.isBaseType(tCls) ) {
                    // FIXME: you can not use interfaces,
                    // you must only use concrete classes.
                    final Collection result = createCollection(returnType,null);

                    for (final String s : resourceService.getProperties(entity,"<" + rdf_property + ">")) {
                        result.add(FacadeUtils.transformToBaseType(s, tCls));
                    }

                    return (C) result;
                } else {
                    throw new IllegalArgumentException("return type is using generic type "+tCls.getName()+", which is not supported in RDF-based collections; please use either Java primitive types or KiWi Entities in KiWiFacades");
                }
            } else {
                throw new IllegalArgumentException("return type is unparametrized collection type "+returnType.getName()+", which is not supported; please use an explicit type parameter in KiWiFacades");
            }
        } else if(inverse) {
            throw new IllegalArgumentException("@RDFInverse not supported for mappings of type "+rdf_property);
        } else {
            throw new IllegalArgumentException("unsupported return type "+returnType.getName());
        }

    }


    private <C> Collection createCollection(Class<C> collectionType, Collection elements) throws IllegalAccessException, InstantiationException {

        final Collection result;

        if(List.class.equals(collectionType)) {
            result = new LinkedList();
        } else if(Set.class.equals(collectionType)) {
            result = new HashSet();
        } else if(Collection.class.equals(collectionType)) {
            result = new HashSet();
        } else {
            result = (Collection) collectionType.newInstance();
        }

        if(elements != null) {
            result.addAll(elements);
        }

        return result;

    }

    /**
     * Return the single object of type C that is reachable from entity by rdf_property. Returns null if there is no such object or
     * if the type of the object does not match the type passed as argument.
     *
     */
    private <C> C queryOutgoingSingle(KiWiResource entity, String rdf_property, Class<C> returnType) throws NamespaceResolvingException, ResourceResolvingException {
        List<KiWiTriple> triples = resourceService.listOutgoing(entity,rdf_property);

        if(triples.size() > 0) {
            KiWiTriple triple = triples.get(0);

            KiWiNode object = triple.getObject();

            if(returnType.isInstance(object)) {
                return (C) object;
            } else {
                log.error("cannot cast retrieved object {} for property {} to return type {}",new Object[] {object,rdf_property,returnType});
                return null;
            }

        } else {
            return null;
        }

    }



    /**
     * Return the single subject of type C that can reach entity by rdf_property.  Returns null if there is no such object or
     * if the type of the object does not match the type passed as argument.
     *
     */
    private <C> C queryIncomingSingle(KiWiResource entity, String rdf_property, Class<C> returnType) throws NamespaceResolvingException, ResourceResolvingException {
        List<KiWiTriple> triples = resourceService.listIncoming(entity, rdf_property);

        if(triples.size() > 0) {
            KiWiTriple triple = triples.get(0);

            KiWiNode subject = triple.getSubject();

            if(returnType.isInstance(subject)) {
                return (C) subject;
            } else {
                log.error("cannot cast retrieved object {} for property {} to return type {}",new Object[] {subject,rdf_property,returnType});
                return null;
            }

        } else {
            return null;
        }

    }



    /**
     * Return the all objects of type C that are reachable from entity by rdf_property. Returns empty set if there is
     * no such object or if the type of the object does not match the type passed as argument.
     *
     */
    private <C> Set<C> queryOutgoingAll(KiWiResource entity, String rdf_property, Class<C> returnType) throws NamespaceResolvingException, ResourceResolvingException {
        List<KiWiTriple> triples = resourceService.listOutgoing(entity,rdf_property);

        Set<C> dupSet = new LinkedHashSet<C>();

        for(KiWiTriple triple : triples) {
            if(returnType.isInstance(triple.getObject())) {
                dupSet.add((C)triple.getObject());
            }
        }

        return dupSet;

    }


    /**
     * Return the all objects of type C that are can reach the entity by rdf_property. Returns empty set if there is
     * no such object or if the type of the object does not match the type passed as argument.
     *
     */
    private <C> Set<C> queryIncomingAll(KiWiResource entity, String rdf_property, Class<C> returnType) throws NamespaceResolvingException, ResourceResolvingException {

        List<KiWiTriple> triples = resourceService.listIncoming(entity,rdf_property);

        Set<C> dupSet = new LinkedHashSet<C>();

        for(KiWiTriple triple : triples) {
            if(returnType.isInstance(triple.getSubject())) {
                dupSet.add((C)triple.getSubject());
            }
        }

        return dupSet;
    }


}
